// $Id: impl_array.cpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#include <algorithm>
#include <cstring>
#include <boost/test/unit_test.hpp>
#include <xpcom/array.hpp>
#include <xpcom/memory.hpp>
#include <xpcom/impl/array.hpp>

namespace xpcom {
namespace {

void do_in_impl_1(const array<int>& a)
{
    BOOST_CHECK(a.empty());
}

void do_in_1(int* a, uint32_t s, uint32_t l)
{
    do_in_impl_1(impl::array_in<int>(a, s, l));
}

void do_in2_1(const int* a, uint32_t s)
{
    do_in_impl_1(impl::array_in2<int>(a, s));
}

void do_in_impl_2(const array<int>& a)
{
    BOOST_CHECK(a.size() == 5);
    for (int i = 0; i < 5; ++i) {
        BOOST_CHECK_EQUAL(a[i], i);
    }
}

void do_in_2(const int* a, uint32_t s, uint32_t l)
{
    do_in_impl_2(impl::array_in<int>(a, s, l));
}

void do_in2_2(int* a, uint32_t s)
{
    do_in_impl_2(impl::array_in2<int>(a, s));
}

void do_out_impl_1(array<int>& a)
{
    BOOST_CHECK(a.empty());
}

void do_out_1(int** a, uint32_t* s, uint32_t* l)
{
    do_out_impl_1(impl::array_out<int>(a, s, l));
}

void do_out2_1(int** a, uint32_t* s)
{
    do_out_impl_1(impl::array_out2<int>(a, s));
}

void do_out_impl_2(array<int>& a)
{
    BOOST_CHECK(a.empty());
    a.reserve(10);
    a.resize(6);
    for (int i = 0; i < 6; ++i) {
        a[i] = i;
    }
}

void do_out_2(int** a, uint32_t* s, uint32_t* l)
{
    do_out_impl_2(impl::array_out<int>(a, s, l));
}

void do_out2_2(int** a, uint32_t* s)
{
    do_out_impl_2(impl::array_out2<int>(a, s));
}

void do_out_impl_3(array<int>& a)
{
    BOOST_CHECK(a.empty());
    a.reserve(10);
}

void do_out_3(int** a, uint32_t* s, uint32_t* l)
{
    do_out_impl_3(impl::array_out<int>(a, s, l));
}

void do_out2_3(int** a, uint32_t* s)
{
    do_out_impl_3(impl::array_out2<int>(a, s));
}

array<int> do_out_impl_retval()
{
    array<int> ret(10);
    ret.reserve(20);
    for (int i = 0; i < 10; i++) {
        ret[i] = i;
    }
    return ret;
}

void do_out_retval(int** a, uint32_t* s, uint32_t* l)
{
    impl::array_out<int> retval(a, s, l);
    retval = do_out_impl_retval();
}

void do_out2_retval(int** a, uint32_t* s)
{
    impl::array_out2<int> retval(a, s);
    retval = do_out_impl_retval();
}

void do_inout_impl_1(array<int>& a)
{
    BOOST_CHECK(a.empty());
    a.reserve(10);
    a.resize(4);
    for (int i = 0; i < 4; ++i) {
        a[i] = i * 10;
    }
}

void do_inout_1(int** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_1(impl::array_inout<int>(a, s, l));
}

void do_inout2_1(int** a, uint32_t* s)
{
    do_inout_impl_1(impl::array_inout2<int>(a, s));
}

void do_inout_impl_2(array<int>& a)
{
    BOOST_CHECK(a.size() == 5);
    for (int i = 0; i < 5; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 10);
    }

    std::reverse(a.begin(), a.end());
    a.resize(4);
}

void do_inout_2(int** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_2(impl::array_inout<int>(a, s, l));
}

void do_inout2_2(int** a, uint32_t* s)
{
    do_inout_impl_2(impl::array_inout2<int>(a, s));
}

void do_inout_impl_3(array<int>& a)
{
    BOOST_CHECK(a.size() == 5);
    for (int i = 0; i < 5; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 10);
    }

    a.clear();
    a.reserve(20);
    a.resize(18);
    for (int i = 0; i < 18; ++i) {
        a[i] = i * 100;
    }
}

void do_inout_3(int** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_3(impl::array_inout<int>(a, s, l));
}

void do_inout2_3(int** a, uint32_t* s)
{
    do_inout_impl_3(impl::array_inout2<int>(a, s));
}

void do_inout_impl_4(array<int>& a)
{
    BOOST_CHECK(a.size() == 10);
    for (int i = 0; i < 10; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 100);
    }

    a.clear();
}

void do_inout_4(int** a, uint32_t* s, uint32_t* l)
{
    do_inout_impl_4(impl::array_inout<int>(a, s, l));
}

void do_inout2_4(int** a, uint32_t* s)
{
    do_inout_impl_4(impl::array_inout2<int>(a, s));
}

void do_out_isize2_impl_1(array<int>& a, uint32_t n)
{
    BOOST_CHECK(a.empty());
    a.resize(n);
    for (int i = 0; i < (int)n; ++i) {
        a[i] = 8;
    }
}

void do_out_isize2_1(int** a, uint32_t n)
{
    do_out_isize2_impl_1(impl::array_out_isize2<int>(a, n), n);
}

void do_out_isize2_impl_2(array<int>& a, uint32_t n)
{
    BOOST_CHECK(a.empty());
    a.resize(n - 1); // to produce less data, cause error
}

void do_out_isize2_2(int** a, uint32_t n)
{
    do_out_isize2_impl_2(impl::array_out_isize2<int>(a, n), n);
}

array<int> do_out_isize2_impl_retval(uint32_t n)
{
    array<int> a(n);
    for (int i = 0; i < (int)n; ++i) {
        a[i] = 8;
    }
    return a;
}

void do_out_isize2_retval(int** a, uint32_t n)
{
    impl::array_out_isize2<int> retval(a, n);
    retval = do_out_isize2_impl_retval(n);
}

}
}

BOOST_AUTO_TEST_CASE(test_impl_array_in)
{
    using namespace xpcom;

    int* a1 = 0;
    int* a2 = memory::alloc<int>(10);
    for (int i = 0; i < 5; ++i) {
        a2[i] = i;
    }

    do_in_1(a1, 0, 0);
    do_in_2(a2, 10, 5);

    memory::free(a2);
}

BOOST_AUTO_TEST_CASE(test_impl_array_in2)
{
    using namespace xpcom;

    int* a1 = 0;
    int* a2 = memory::alloc<int>(10);
    for (int i = 0; i < 5; ++i) {
        a2[i] = i;
    }

    do_in2_1(a1, 0);
    do_in2_2(a2, 5);

    memory::free(a2);
}

BOOST_AUTO_TEST_CASE(test_impl_array_out)
{
    using namespace xpcom;

    int* a = 0;
    uint32_t s;
    uint32_t l;

    do_out_1(&a, &s, &l);
    BOOST_CHECK(a == 0);
    BOOST_CHECK(s == 0);
    BOOST_CHECK(l == 0);

    do_out_2(&a, &s, &l);
    BOOST_CHECK(l == 6);
#ifdef XPCOM_ENABLE_LENGTH_IS
    BOOST_CHECK(s == 10);
#endif
    for (int i = 0; i < 6; ++i) {
        BOOST_CHECK_EQUAL(a[i], i);
    }
    memory::free(a);

    do_out_3(&a, &s, &l);
    BOOST_CHECK(l == 0);
#ifdef XPCOM_ENABLE_LENGTH_IS
    BOOST_CHECK(s == 10);
#endif
    memory::free(a);

    do_out_retval(&a, &s, &l);
    BOOST_CHECK(l = 10);
    for (int i = 0; i < 10; ++i) {
        BOOST_CHECK_EQUAL(a[i], i);
    }
    memory::free(a);
}

BOOST_AUTO_TEST_CASE(test_impl_array_out2)
{
    using namespace xpcom;

    int* a = 0;
    uint32_t s;

    do_out2_1(&a, &s);
    BOOST_CHECK(a == 0);
    BOOST_CHECK(s == 0);

    do_out2_2(&a, &s);
    BOOST_CHECK(s == 6);
    for (int i = 0; i < 6; ++i) {
        BOOST_CHECK_EQUAL(a[i], i);
    }
    memory::free(a);

    do_out2_3(&a, &s);
    BOOST_CHECK(s == 0);
    memory::free(a);

    do_out2_retval(&a, &s);
    BOOST_CHECK(s = 10);
    for (int i = 0; i < 10; ++i) {
        BOOST_CHECK_EQUAL(a[i], i);
    }
    memory::free(a);
}

BOOST_AUTO_TEST_CASE(test_impl_array_inout)
{
    using namespace xpcom;

    int* a = 0;
    uint32_t s = 0;
    uint32_t l = 0;

    do_inout_1(&a, &s, &l);
    BOOST_CHECK(l == 4);
#ifdef XPCOM_ENABLE_LENGTH_IS
    BOOST_CHECK(s == 10);
#endif
    for (int i = 0; i < 4; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 10);
    }

    a = memory::realloc(a, 20);
    s = 20;
    l = 5;
    a[4] = 40;
    do_inout_2(&a, &s, &l);
    BOOST_CHECK(l == 4);
    BOOST_CHECK_EQUAL(a[0], 40);
    BOOST_CHECK_EQUAL(a[1], 30);
    BOOST_CHECK_EQUAL(a[2], 20);
    BOOST_CHECK_EQUAL(a[3], 10);

    a = memory::realloc(a, 8);
    s = 8;
    l = 5;
    for (int i = 0; i < 5; ++i) {
        a[i] = i * 10;
    }
    do_inout_3(&a, &s, &l);
    BOOST_CHECK(l == 18);
#ifdef XPCOM_ENABLE_LENGTH_IS
    BOOST_CHECK(s == 20);
#endif
    for (int i = 0; i < 18; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 100);
    }

    a = memory::realloc(a, 12);
    s = 12;
    l = 10;
    do_inout_4(&a, &s, &l);
    BOOST_CHECK(l == 0);
    memory::free(a);
}

BOOST_AUTO_TEST_CASE(test_impl_array_inout2)
{
    using namespace xpcom;

    int* a = 0;
    uint32_t s = 0;

    do_inout2_1(&a, &s);
    BOOST_CHECK(s == 4);
    for (int i = 0; i < 4; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 10);
    }

    a = memory::realloc(a, 5);
    s = 5;
    a[4] = 40;
    do_inout2_2(&a, &s);
    BOOST_CHECK(s == 4);
    BOOST_CHECK_EQUAL(a[0], 40);
    BOOST_CHECK_EQUAL(a[1], 30);
    BOOST_CHECK_EQUAL(a[2], 20);
    BOOST_CHECK_EQUAL(a[3], 10);

    a = memory::realloc(a, 15);
    s = 5;
    for (int i = 0; i < 5; ++i) {
        a[i] = i * 10;
    }
    do_inout2_3(&a, &s);
    BOOST_CHECK(s == 18);
    for (int i = 0; i < 18; ++i) {
        BOOST_CHECK_EQUAL(a[i], i * 100);
    }

    a = memory::realloc(a, 12);
    s = 10;
    do_inout2_4(&a, &s);
    BOOST_CHECK(s == 0);
    memory::free(a);
}

BOOST_AUTO_TEST_CASE(test_impl_array_out_isize2)
{
    using namespace xpcom;

    int* a = 0;

    do_out_isize2_1(&a, 8);
    for (int i = 0; i < 8; ++i) {
        BOOST_CHECK_EQUAL(a[i], 8);
    }
    memory::free(a);

    do_out_isize2_1(&a, 0);
    BOOST_CHECK(a == 0);

#ifdef NDEBUG // to avoid assertion failure
    do_out_isize2_2(&a, 5); // returned length less than requested
    BOOST_CHECK(a == 0);
#endif

    do_out_isize2_retval(&a, 10);
    for (int i = 0; i < 10; ++i) {
        BOOST_CHECK_EQUAL(a[i], 8);
    }
    memory::free(a);
}

